package com.redxun.filter;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.redxun.core.api.config.ICoreConfigService;
import com.redxun.core.auth.properties.SecurityProperties;
import com.redxun.core.boot.constant.CacheConstant;
import com.redxun.core.common.constant.SysConstant;
import com.redxun.core.common.utils.SpringUtil;
import com.redxun.core.tool.StringUtils;
import com.redxun.util.XssCleanRuleUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: hj
 * @Description: 自定义防XSS攻击网关全局过滤器
 * @Date: Created in 2020/7/4.
 */
@Slf4j
public class XssRequestGlobalFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse)servletResponse;
        
        // grab configuration from Config object
        log.debug("----自定义防XSS攻击网关全局过滤器生效----");
        
        String reqUrl= httpRequest.getRequestURL().toString();
        
        //校验特殊url拦截
        checkSecurityurls(reqUrl);
        
        //Referer拦截
        handCrsf(httpRequest);
        
        handXss(httpRequest, httpResponse, filterChain,reqUrl);
    }
    
    private void handCrsf(HttpServletRequest httpRequest) {
        ICoreConfigService configService = SpringUtil.getBean(ICoreConfigService.class);
        String config = configService.getConfig(CacheConstant.REFERER_SOURCE, CacheConstant.DEFAULT_GROUP, SysConstant.CONFIG_TIMEOUT);
        //没有配置referer白名单校验，则跳过
        if (StringUtils.isEmpty(config)) {
            return ;
        }
        JSONObject refererSource = JSONObject.parseObject(config);
        Boolean isCheckReferer = refererSource.getBoolean("isReferer");
        //没有开启referer白名单校验，则跳过
        if (!isCheckReferer) {
            return ;
        }
        
        String refererUrl = httpRequest.getHeader(HttpHeaders.REFERER);
        if (StringUtils.isEmpty(refererUrl)) {
            return ;
        }
        String[] refererUrlList = refererUrl.replace("www.","").split("[/]");
        String domainName =refererUrlList[2];
        
        String refererList =refererSource.getString("refererList");
        //没有配置referer白名单校验，则跳过
        if (StringUtils.isEmpty(refererList)) {
            return ;
        }
        String rfWhiteUrl= refererList.replace("http://","").replace("https://","");
        JSONArray rfWhiteUrlList= JSONArray.parseArray(rfWhiteUrl);
        for (Object rfUrlObj:rfWhiteUrlList) {
            String[] rfUrls = rfUrlObj.toString().split("[/]");
            if (rfUrls[0].equals(domainName)) {
                return ;
            }
        }
        String message="Referer请求非法：RefererUrl="+refererUrl;
        log.error(message);
        throw new IllegalStateException(message);
    }
    
    private void checkSecurityurls(String reqUrl) {
        //忽略xss攻击地址
        final String constStr = "..";
        if (reqUrl.indexOf(constStr) > -1) {
            String message="请求路径包含非法字符：url="+reqUrl;
            log.error(message);
            throw new IllegalStateException(message);
        }
    }
    
    private void handXss(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain, String reqUrl)
            throws ServletException, IOException {
        SecurityProperties securityProperties = SpringUtil.getBean(SecurityProperties.class);
        String method = httpRequest.getMethod();
        String contentType = httpRequest.getHeader(HttpHeaders.CONTENT_TYPE);
        //忽略xss攻击地址
        if (securityProperties.getIgnore().containsXssUrl(reqUrl)) {
            try {
                filterChain.doFilter(httpRequest, httpResponse);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ServletException e) {
                e.printStackTrace();
            }
        }
        
        Boolean postFlag = (HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method))
                &&  (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)
                || MediaType.APPLICATION_JSON_VALUE.equals(contentType)
                || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType));
        
        // get 请求， 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
        if (HttpMethod.GET.matches(method)) {
            String rawQuery = httpRequest.getQueryString();
            if (StringUtils.isBlank(rawQuery)) {
                try {
                    filterChain.doFilter(httpRequest, httpResponse);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ServletException e) {
                    e.printStackTrace();
                }
            }
            rawQuery = XssCleanRuleUtils.xssClean(rawQuery);
            try {
                try {
                    filterChain.doFilter(httpRequest, httpResponse);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ServletException e) {
                    e.printStackTrace();
                }
            } catch (Exception e) {
                log.error("get请求清理xss攻击异常", e);
                throw new IllegalStateException("Invalid URI query: \"" + rawQuery + "\"");
            }
        }
        //post请求时，如果是文件上传之类的请求，不修改请求消息体
        else if (postFlag) {
            // 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
            filterChain.doFilter(httpRequest, httpResponse);
        } else {
            filterChain.doFilter(httpRequest, httpResponse);
        }
        
    }
    
}
